/*
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 *  02111-1307, USA.
 *
 * Description:
 *   Savepoints.
 */

#include "x_saves.h"
#include "x_main.h"
#include "g_game.h"
#include "lprintf.h"
#include "p_map.h"
#include "p_saveg.h"
#include "r_demo.h"
#include "r_draw.h"
#include "r_fps.h"
#include "r_main.h"
#include "s_sound.h"
#include "s_advsound.h"

#define MIN_MAXPLAYERS 32

extern void RecalculateDrawnSubsectors(void);
extern int totalleveltimes;
extern dboolean setsizeneeded;
extern unsigned char *savebuffer;
extern size_t savegamesize;

int x_getSavepointIndex(int dest);

static struct {
    int tic;
    unsigned char *save;
} savepoints[MAXSSP]; // autosavepoint at 0

void x_clearMapSavepoints(void)
{
    int i;
    for (i = 1; i < MAXSSP; i++) {
        free(savepoints[i].save);
        savepoints[i].save = NULL;
        savepoints[i].tic = 0;
    }
}

void x_clearUserSavepoint(void)
{
    free(savepoints[0].save);
    savepoints[0].save = NULL;
    savepoints[0].tic = 0;
}

int x_getSavepointIndex(int dest)
{
    int index = -1, savetic = 0, map = gamemap;

    if (map > MAXSSP - 1)
        map = MAXSSP - 1;

    if (savepoints[0].save && savepoints[0].tic < dest) {
        index = 0;
        savetic = savepoints[0].tic;
    }
    for (; map > 0; map--) {
        if (savetic < savepoints[map].tic && savepoints[map].tic < dest) {
            index = map;
            break;
        }
    }
    return index;
}

int x_getSavepointTic(int dest)
{
    int index = x_getSavepointIndex(dest);
    return index < 0 ? 0 : savepoints[index].tic;
}

int x_makeSavepoint(int isUserSave)
{
    int i, map = gamemap;

    if (map < 0 || map > MAXSSP - 1 || gamestate != GS_LEVEL ||
      gameaction != ga_nothing)
        return 1;

    if (leveltime != 1 || isUserSave)
        map = 0;

    if (savepoints[map].save) {
        free(savepoints[map].save);
        savepoints[map].save = NULL;
    }

    savepoints[map].save = malloc(savegamesize);
    savepoints[map].tic = gametic;
    save_p = savebuffer = savepoints[map].save;

    // do save
    *save_p++ = compatibility_level;

    *save_p++ = gameskill;
    *save_p++ = gameepisode;
    *save_p++ = gamemap;

    for (i = 0; i < MAXPLAYERS; i++)
        *save_p++ = playeringame[i];

    for (; i < MIN_MAXPLAYERS; i++)
        *save_p++ = 0;

    *save_p++ = idmusnum;

    save_p = G_WriteOptions(save_p);

    memcpy(save_p, &leveltime, sizeof(leveltime));
    save_p += sizeof(leveltime);

    memcpy(save_p, &totalleveltimes, sizeof(totalleveltimes));
    save_p += sizeof(totalleveltimes);

    *save_p++ = (gametic - basetic) & 255;

    Z_CheckHeap();
    P_ArchivePlayers();
    Z_CheckHeap();

    P_ThinkerToIndex();

    P_ArchiveWorld();
    Z_CheckHeap();
    P_ArchiveThinkers();
    Z_CheckHeap();
    P_ArchiveSpecials();

    P_IndexToThinker();

    P_ArchiveRNG();
    Z_CheckHeap();
    P_ArchiveMap();

    *save_p++ = 0xe6;

    Z_CheckHeap();
    // done

    // in case checksavegame reallocated savebuffer
    savepoints[map].save = savebuffer;
    save_p = savebuffer = NULL;
    return 0;
}

// modified G_DoLoadGame
int x_loadSavepoint(int dest)
{
    int i, map = x_getSavepointIndex(dest);
    save_p = NULL;

    if (map < 0)
        return 1;

    if (map)
        x_clearUserSavepoint();

    save_p = savepoints[map].save;
    gametic = savepoints[map].tic;
    current_tic = gametic - 1;

    gameaction = ga_nothing;

    compatibility_level = *save_p++;

    gameskill = *save_p++;
    gameepisode = *save_p++;
    gamemap = *save_p++;

    for (i = 0; i < MAXPLAYERS; i++)
        playeringame[i] = *save_p++;

    save_p += MIN_MAXPLAYERS - MAXPLAYERS;

    idmusnum = *save_p++;
    if (idmusnum == 255)
        idmusnum = -1;

    save_p += (G_ReadOptions(save_p) - save_p);

    G_InitNew(gameskill, gameepisode, gamemap);

    memcpy(&leveltime, save_p, sizeof(leveltime));
    save_p += sizeof(leveltime);

    memcpy(&totalleveltimes, save_p, sizeof(totalleveltimes));
    save_p += sizeof(totalleveltimes);

    basetic = gametic - *save_p++;

    P_MapStart();
    P_UnArchivePlayers();
    P_UnArchiveWorld();
    P_UnarchiveThinkersAndSpecials();
    P_UnArchiveRNG();
    P_UnArchiveMap();
    P_MapEnd();
    R_ActivateSectorInterpolations();
    R_SmoothPlaying_Reset(NULL);

    if (musinfo.current_item != -1)
        S_ChangeMusInfoMusic(musinfo.current_item, true);

    RecalculateDrawnSubsectors();

    if (*save_p != 0xe6)
        I_Error("x_loadSavepoint: Bad savepoint on map %i", map);

    if (setsizeneeded)
        R_ExecuteSetViewSize();

    R_FillBackScreen();

    save_p = NULL;

    return 0;
}

